/**
 * Copyright 2011 Mathieu Magnaudet
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package edu.enac.geoapp.services;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.log4j.Logger;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.data.DataSourceException;
import org.geotools.data.FeatureSource;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.factory.Hints;
import org.geotools.gce.geotiff.GeoTiffReader;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.xml.sax.SAXException;

import com.keithpower.gekmlib.Document;
import com.keithpower.gekmlib.KMLParser;
import com.keithpower.gekmlib.Kml;
import com.keithpower.gekmlib.Placemark;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;

import edu.enac.geoapp.parser.pln.FlightPlanSaxParser;
import edu.enac.geoapp.parser.pln.MapPoint;
import edu.enac.geoapp.wims.WimsParser;
import edu.enac.geoapp.wims.exception.WimsException;
import edu.enac.geoapp.wims.model.WeatherProduct;

/**
 * Retrieves meteorological data from various file format
 * @author mathieu
 *
 */
public class DataProvider {
	private static final DataProvider INSTANCE = new DataProvider();
	private static Logger logger = Logger.getLogger(DataProvider.class);
	private static GeometryFactory geometryFactory = JTSFactoryFinder
			.getGeometryFactory(null);
	private static FileStore fileStore = FileStore.getInstance();
	private Map<String, String> metarList;
	private Map<String, String> tarList;

	private DataProvider() {
	}

	/**
	 * Parses the METAR data contained in the file referenced in the {@link FileStore}
	 * @throws ServiceException
	 */
	public void parseMetar() throws ServiceException {
		metarList = new HashMap<String, String>();
		try {

			FileReader reader = new FileReader(fileStore.getPathToMetar());
			BufferedReader buffReader = new BufferedReader(reader);
			StringTokenizer tokenizer;
			String airport;
			while (buffReader.ready()) {
				String line = buffReader.readLine();
				if (!line.isEmpty()) {
					tokenizer = new StringTokenizer(line);
					airport = tokenizer.nextToken();
					StringBuffer content;
					if (metarList.containsKey(airport)) {
						content = new StringBuffer(metarList.get(airport));
					} else {
						content = new StringBuffer();
					}
					while (tokenizer.hasMoreTokens()) {
						content.append(tokenizer.nextToken() + " ");
					}
					content.append("\n");
					metarList.put(airport, content.toString());
				}
			}
			buffReader.close();
			reader.close();
		} catch (FileNotFoundException e) {
			throw new ServiceException("No file available for METAR data");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Parses the TAF data contained in the file referenced in the {@link FileStore}
	 * @throws ServiceException
	 */
	public void parseTaf() throws ServiceException {
		tarList = new HashMap<String, String>();
		try {

			FileReader reader = new FileReader(fileStore.getPathToTaf());
			BufferedReader buffReader = new BufferedReader(reader);
			StringTokenizer tokenizer;
			String airport;
			while (buffReader.ready()) {
				String line = buffReader.readLine();
				if (!line.isEmpty()) {
					tokenizer = new StringTokenizer(line);
					airport = tokenizer.nextToken();
					StringBuffer content;
					if (tarList.containsKey(airport)) {
						content = new StringBuffer(tarList.get(airport));
					} else {
						content = new StringBuffer();
					}
					while (tokenizer.hasMoreTokens()) {
						content.append(tokenizer.nextToken() + " ");
					}
					content.append("\n");
					tarList.put(airport, content.toString());
				}
			}
			buffReader.close();
			reader.close();
		} catch (FileNotFoundException e) {
			throw new ServiceException("No file available for TAF data");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/**
	 * Retrieves data from a given Geotiff file and put them 
	 * in a {@link GridCoverage2D}
	 * 
	 * @param file the file to parse
	 * @return the builded {@link GridCoverage2D}
	 */
	private GridCoverage2D getGeotiffData(String file) {
		GeoTiffReader reader;
		try {
			reader = new GeoTiffReader(file, new Hints(
					Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE));
		} catch (DataSourceException ex) {
			return null;
		} catch (IllegalArgumentException e) {
			return null;
		}

		GridCoverage2D coverage;
		try {
			coverage = (GridCoverage2D) reader.read(null);
		} catch (IOException ex) {
			ex.printStackTrace();
			return null;
		}

		return coverage;

	}

	/**
	 * Builds a {@link GridCoverage2D} from the Geotiff Radar file referenced in the {@link FileStore}
	 * @return the {@link GridCoverage2D}
	 */
	public GridCoverage2D getRadar() {
		return getGeotiffData(fileStore.getPathToRadar());
	}

	/**
	 * Builds a {@link GridCoverage2D} from the Geotiff SatIr file referenced in the {@link FileStore}
	 * @return the {@link GridCoverage2D}
	 */
	public GridCoverage2D getSatIr() {
		return getGeotiffData(fileStore.getPathToSatIr());
	}

	/**
	 * Builds a {@link GridCoverage2D} from the Geotiff SatVis file referenced in the {@link FileStore}
	 * @return the {@link GridCoverage2D}
	 */
	public GridCoverage2D getSatVis() {
		return getGeotiffData(fileStore.getPathToSatVis());
	}

	/**
	 * Build a map with the countries boundaries
	 * 
	 * @return the map as a {@link FeatureSource}
	 * @throws IOException
	 */
	public FeatureSource<SimpleFeatureType, SimpleFeature> getWorld()
			throws IOException {
		File world = new File(fileStore.getPathToWorldFile());
		URL shapeURL = world.toURI().toURL();
		ShapefileDataStore store = new ShapefileDataStore(shapeURL);
		String name = store.getTypeNames()[0];

		return store.getFeatureSource(name);
	}

	/**
	 * Retrieves data from a GML file 
	 * 
	 * @param path to the file
	 * @return the data as a {@link WeatherProduct}
	 * @throws IOException
	 * @throws WimsException
	 */
	public WeatherProduct getMeteoObjects(String path) throws IOException,
			WimsException {
		WimsParser parser = new WimsParser();
		parser.parse(path);
		return parser.getProduct();
	}

	/**
	 * Retrieves airports name and position
	 * 
	 * @return the data as a {@link Map} name to position
	 */
	public Map<String, com.vividsolutions.jts.geom.Point> getAirports() {
		Map<String, com.vividsolutions.jts.geom.Point> airports = new HashMap<String, com.vividsolutions.jts.geom.Point>();
		File f = new File(fileStore.getPathToAirportsFile());
		Kml kmlRoot = null;
		try {
			kmlRoot = new KMLParser().parse(f);
		} catch (IOException e) {
			logger.fatal(e.getMessage());
			System.exit(0);
		} catch (SAXException e) {
			logger.fatal(e.getMessage());
			System.exit(0);
		}
		Document d = kmlRoot.getDocument();
		Placemark places[] = d.getPlacemarks();
		for (Placemark p : places) {
			com.vividsolutions.jts.geom.Point coord;
			String n = p.getName().split(" ")[0];
			String coordStr = p.getPoint().getCoordinates();
			String s[] = coordStr.split(",");
			float lat, lon;
			lon = Float.parseFloat(s[0]);
			lat = Float.parseFloat(s[1]);
			coord = geometryFactory.createPoint(new Coordinate(lat, lon));
			airports.put(n, coord);
		}

		return airports;
	}

	/**
	 * Récupère le plan de vol principal contenu dans le fichier fournit au
	 * format arinc 633
	 * 
	 * @param path
	 * @return
	 */
	public LineString getPrimaryFlightPlan(String path) {
		FlightPlanSaxParser parser = new FlightPlanSaxParser();
		parser.parse(path);
		List<MapPoint> mapPointList = parser.getPrimaryFlightPlan();
		Coordinate coords[] = new Coordinate[mapPointList.size()];

		// convert mapPointlist to pointList
		int i = 0;
		for (MapPoint point : mapPointList) {
			coords[i++] = new Coordinate(point.getLatDegDec(),
					point.getLonDegDec());
		}
		return geometryFactory.createLineString(coords);
	}

	/**
	 * Récupère le plan de vol alternatif contenu dans le fichier fournit au
	 * format arinc 633
	 * 
	 * @param path
	 * @return
	 */
	public LineString getAlternateFlightPlan(String path) {
		FlightPlanSaxParser parser = new FlightPlanSaxParser();
		parser.parse(path);
		List<MapPoint> mapPointList = parser.getAlternateFlightPlan();
		Coordinate coords[] = new Coordinate[mapPointList.size()];

		// convert mapPointlist to pointList
		int i = 0;
		for (MapPoint point : mapPointList) {
			coords[i++] = new Coordinate(point.getLatDegDec(),
					point.getLonDegDec());
		}
		return geometryFactory.createLineString(coords);
	}

	public int[] getRangeValues() {
		return new int[] { 10, 20, 30, 50 };
	}

	/**
	 * METAR corresponding to an airport
	 * @param airport
	 * @return
	 */
	public String getMetar(String airport) {
		if (metarList == null || metarList.isEmpty()) {
			try {
				parseMetar();
			} catch (ServiceException e) {
				return "No data";
			}
		}
		if (metarList.get(airport) != null)
			return metarList.get(airport);
		else
			return "No data";
	}

	/**
	 * TAR corrsponding to an airport
	 * @param airport
	 * @return
	 */
	public String getTar(String airport) {
		if (tarList == null || tarList.isEmpty()) {
			try {
				parseTaf();
			} catch (ServiceException e) {
				return "No data";
			}
		}
		if (tarList.get(airport) != null)
			return tarList.get(airport);
		else
			return "No data";
	}

	public static DataProvider getInstance() {
		return INSTANCE;
	}

}